package org.yunai.swjg.server.module.scene.core;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.yunai.swjg.server.core.annotation.SceneThread;
import org.yunai.swjg.server.core.message.GameMessage;
import org.yunai.swjg.server.core.service.Online;
import org.yunai.swjg.server.core.service.OnlineContextService;
import org.yunai.swjg.server.module.player.vo.Player;
import org.yunai.swjg.server.module.scene.core.persistance.SceneDataUpdater;
import org.yunai.swjg.server.module.scene.core.template.SceneTemplate;
import org.yunai.swjg.server.rpc.message.S_C.*;
import org.yunai.swjg.server.rpc.struct.StPlayerInfo;
import org.yunai.yfserver.common.HeartBeatable;
import org.yunai.yfserver.common.LoggerFactory;
import org.yunai.yfserver.common.Tickable;
import org.yunai.yfserver.message.IMessage;
import org.yunai.yfserver.message.MessageQueue;
import org.yunai.yfserver.spring.BeanManager;
import org.yunai.yfserver.util.Assert;
import org.yunai.yfserver.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

/**
 * 场景基类
 * User: yunai
 * Date: 13-5-13
 * Time: 下午11:53
 */
public abstract class AbstractScene implements Tickable, HeartBeatable {

    private static final Logger LOGGER_UPDATER = LoggerFactory.getLogger(LoggerFactory.Logger.updater, AbstractScene.class);
    private static final Logger LOGGER_SCENE = LoggerFactory.getLogger(LoggerFactory.Logger.scene, AbstractScene.class);

    protected static OnlineContextService onlineContextService;

    static {
        onlineContextService = BeanManager.getBean(OnlineContextService.class);
    }

    /**
     * 场景编号
     */
    protected Integer sceneId;
    /**
     * 线编号
     */
    protected Integer line;
    /**
     * 场景模版
     */
    private SceneTemplate sceneTemplate;
    /**
     * 消息队列
     */
    private MessageQueue msgQueue;
    /**
     * 场景玩家管理
     */
    protected SceneOnlineManager sceneOnlineManager;
    /**
     * 场景数据更新器
     */
    private SceneDataUpdater dataUpdater;

    public AbstractScene(SceneTemplate sceneTemplate) {
        Assert.notNull(sceneTemplate, "sceneTemplate can't be null!");

        this.sceneTemplate = sceneTemplate;
        this.sceneId = sceneTemplate.getId();
        this.sceneOnlineManager = new SceneOnlineManager(this);
        this.msgQueue = new MessageQueue();

        // TODO 未初始化
//        monsterManager = new SceneMonsterManager(this, monsterService);
//        listeners = new ArrayList<SceneListener>(
//                SceneListener.DEFAULT_LISTENER_CONTAINER_CAPACITY);
//        hbTaskExecutor = new HeartbeatTaskExecutorImpl();
//
        this.dataUpdater = new SceneDataUpdater();
    }

    public Integer getSceneId() {
        return sceneId;
    }

    public void setSceneId(Integer sceneId) {
        this.sceneId = sceneId;
    }

    public Integer getLine() {
        return line;
    }

    public void setLine(Integer line) {
        this.line = line;
    }

    @Override
    public void tick() {
        // 处理场景消息
        while (!msgQueue.isEmpty()) {
            msgQueue.get().execute();
        }
        // 场景玩家tick
        sceneOnlineManager.tick();
        // 心跳
        heartBeat();
    }

    @Override
    public void heartBeat() {
        sceneOnlineManager.heartBeat();
        // monsterManager.heartBeat();
        this.updateData();
        // hbTaskExecutor.onHeartBeat();
    }

    /**
     * 添加消息到场景消息队列
     *
     * @param msg 消息
     */
    public void putMessage(IMessage msg) {
        if (msg == null) {
            return;
        }
        this.msgQueue.put(msg);
    }

    /**
     * 更新数据
     */
    private void updateData() {
        try {
            this.dataUpdater.process();
        } catch (Exception e) {
            LOGGER_UPDATER.error("[updateData] [error:{}]", ExceptionUtils.getStackTrace(e));
        }
    }

    /**
     * 添加场景中玩家
     *
     * @param online 在线信息
     */
    public void addOnline(Online online) {
        sceneOnlineManager.add(online.getPlayer().getId());
    }

    /**
     * 移除场景中玩家
     *
     * @param online 在线信息
     */
    public void removeOnline(Online online) {
        sceneOnlineManager.remove(online.getPlayer().getId());
    }

    /**
     * @param playerId 玩家编号
     * @return 玩家信息
     */
    public Online getOnline(Integer playerId) {
        return sceneOnlineManager.getOnline(playerId);
    }

    /**
     * @return 消息队列是否为空
     */
    public boolean isMessageEmpty() {
        return msgQueue.isEmpty();
    }

    /**
     * 子类各自实现，判断该场景玩家是否满了
     *
     * @return 场景是否已满
     */
    public abstract boolean isFull();

    /**
     * @return 场景是否为空
     */
    public boolean isEmpty() {
        return playerSize() == 0;
    }

    /**
     * @return 场景人数
     */
    public int playerSize() {
        return sceneOnlineManager.size();
    }

    /**
     * @return 场景类型
     */
    public final SceneDef.Type getType() {
        return sceneTemplate.getType();
    }

    /**
     * 玩家进入场景<br />
     * <pre>
     *     1. 从场景中加入玩家
     *     2. 发送进入场景消息给自己
     *     3. 发送玩家进入场景消息给场景中其他玩家
     *     4. 将场景中的其他玩家信息发送给玩家
     * </pre>
     *
     * @param online 玩家在线信息
     */
    @SceneThread
    public void onPlayerEnter(Online online) {
        // 从场景中加入该玩家
        Player player = online.getPlayer();
        LOGGER_SCENE.debug("[onPlayerEnter] [player:{} enter scene: {} line: {}].", player.getId(), sceneId, line);
        // 发送给自己进入场景成功
        S_C_SceneEnterSuccessResp sceneEnterSuccessResp = new S_C_SceneEnterSuccessResp();
        online.write(sceneEnterSuccessResp);
        // 发送其他玩家信息给自己
        Set<Integer> playerIdSet = sceneOnlineManager.getPlayerIds();
        List<StPlayerInfo> playerInfos = new ArrayList<>(playerIdSet.size());
        for (Integer playerId : playerIdSet) {
            Online otherOnline = sceneOnlineManager.getOnline(playerId);
            if (otherOnline != null) {
                playerInfos.add(otherOnline.getPlayer().toStPlayerInfo());
            }
        }
        S_C_ScenePlayerListReq scenePlayerListReq = new S_C_ScenePlayerListReq(playerInfos);
        online.write(scenePlayerListReq);
        // 发送自己进入给其他玩家
        StPlayerInfo playerInfo = player.toStPlayerInfo();
        S_C_ScenePlayerEnterReq scenePlayerEnterReq = new S_C_ScenePlayerEnterReq(playerInfo);
        sceneOnlineManager.sendGameMessage(scenePlayerEnterReq, CollectionUtils.asSet(player.getId()));
    }

    /**
     * 玩家离开场景<br />
     * <pre>
     *     1. 从场景中移除玩家
     *     2. 发送玩家离开场景给场景中其他玩家
     * </pre>
     *
     * @param online 在线信息
     */
    @SceneThread
    @SuppressWarnings("unchecked")
    public void onPlayerLevel(Online online) {
        // 从场景中移除该玩家
        Player player = online.getPlayer();
        removeOnline(online);
        LOGGER_SCENE.debug("[onPlayerLevel] [player:{} leave scene: {} line: {}].", player.getId(), sceneId, line);
        // 发送自己离开给其他玩家
        sceneOnlineManager.sendGameMessage(new S_C_ScenePlayerLeaveReq(player.getId()), Collections.EMPTY_SET);
        online.setScene(null);
    }

    /**
     * 玩家场景移动<br />
     * <pre>
     *     1. 发送移动消息给场景中其他玩家
     * </pre>
     *
     * @param online   在线信息
     * @param fromSceneX 来源场景坐标X
     * @param fromSceneY 来源场景坐标Y
     * @param toSceneX 目标场景地址X
     * @param toSceneY 目标场景地址Y
     */
    @SceneThread
    public void onPlayerMove(Online online, Short fromSceneX, Short fromSceneY, Short toSceneX, Short toSceneY) {
        Integer playerId = online.getPlayer().getId();
        LOGGER_SCENE.debug("[onPlayerMove] [player({}) move to [{}, {}]].", playerId, toSceneX, toSceneY);
        // 发送移动消息给其他玩家
        sceneOnlineManager.sendGameMessage(new S_C_ScenePlayerMoveReq(fromSceneX, fromSceneY, playerId, toSceneX, toSceneY), CollectionUtils.asSet(playerId));
    }

    /**
     * 广播消息给场景中的玩家<br />
     * 该方法请在场景线程中调用
     *
     * @param msg 消息
     */
    @SceneThread
    public void broadcastMessage(final GameMessage msg) {
        broadcastMessage(msg, CollectionUtils.emptySet(Integer.class));
    }

    /**
     * 广播消息给场景中的玩家<br />
     * 该方法请在场景线程中调用
     *
     * @param msg 消息
     * @param exPlayerIds 不广播的玩家集合
     */
    @SceneThread
    public void broadcastMessage(final GameMessage msg, Set<Integer> exPlayerIds) {
//        msgQueue.put(new SysInternalMessage() {
//            @Override
//            public short getCode() {
//                return 0;
//            }
//
//            @Override
//            public void execute() {
//                sceneOnlineManager.sendGameMessage(msg, CollectionUtils.emptySet(Integer.class));
//            }
//        });
        sceneOnlineManager.sendGameMessage(msg, exPlayerIds);
    }
}
